package dai.core.base.loader

import dai.core.base.Material
import dai.core.base.Mesh
import dai.core.base.Utils
import org.joml.Vector2f
import org.joml.Vector3f
import kotlin.collections.ArrayList

class IdxGroup(
    var idxPos:Int = NO_VALUE,
    var idxTexCoord:Int = NO_VALUE,
    var idxNormal:Int = NO_VALUE
){
    companion object{
        const val NO_VALUE = -1
    }
}

class Face(v1:String,v2:String,v3:String) {

    val idxGroups = ArrayList<IdxGroup>(3)

    init {
        idxGroups.add(parseLine(v1))
        idxGroups.add(parseLine(v2))
        idxGroups.add(parseLine(v3))
    }

    private fun parseLine(line: String): IdxGroup {
        val tokens = line.split("/")
        val len = tokens.size

        val idxGroup = IdxGroup()
        idxGroup.idxPos = tokens[0].toInt() - 1
        if (len > 1){
            // It can be empty if the obj does not define text coords
            val texCoord = tokens[1]
            if (texCoord.isNotEmpty()){
                idxGroup.idxTexCoord = texCoord.toInt() - 1
            }else{
                idxGroup.idxTexCoord = IdxGroup.NO_VALUE
            }
            if (len > 2){
                idxGroup.idxNormal = tokens[2].toInt() - 1
            }
        }
        return idxGroup
    }
}


object ObjLoader {

    fun loadMesh(fileName:String) : Mesh{
        val lines = Utils.readAllLines(fileName)

        val vertices = ArrayList<Vector3f>()
        val texCoords = ArrayList<Vector2f>()
        val normals = ArrayList<Vector3f>()
        val faces = ArrayList<Face>()

        for (line in lines){
            val tokens = line.split(Regex("\\s+"))
            when(tokens[0]){
                "v" -> {
                    val v = Vector3f(
                        tokens[1].toFloat(),
                        tokens[2].toFloat(),
                        tokens[3].toFloat()
                    )
                    vertices.add(v)
                }

                "vt" ->{
                    val vt = Vector2f(
                        tokens[1].toFloat(),
                        tokens[2].toFloat()
                    )
                    texCoords.add(vt)
                }

                "vn" ->{
                    val vn = Vector3f(
                        tokens[1].toFloat(),
                        tokens[2].toFloat(),
                        tokens[3].toFloat()
                    )
                    normals.add(vn)
                }

                "f" ->{
                    val face = Face(tokens[1],tokens[2],tokens[3])
                    faces.add(face)
                }
            }
        }
        return reorderLists(vertices,texCoords,normals,faces)
    }

    private fun reorderLists(vertices: ArrayList<Vector3f>,
                             texCoords: ArrayList<Vector2f>,
                             normals: ArrayList<Vector3f>,
                             faces: ArrayList<Face>) : Mesh{
        val dstIndices = ArrayList<Int>()
        val posArr = FloatArray(vertices.size * 3)
        vertices.forEachIndexed { index, pos ->
            posArr[index * 3] = pos.x
            posArr[index * 3 + 1] = pos.y
            posArr[index * 3 + 2] = pos.z
        }

        val texCoordArr = FloatArray(vertices.size * 2)
        val normArr = FloatArray(vertices.size * 3)
        faces.forEach { f->
            f.idxGroups.forEach { idxGroup ->
                processFaceVertex(idxGroup,texCoords,normals,dstIndices,texCoordArr,normArr)
            }
        }

        val indiceArr = Utils.arrayListToArray(dstIndices)
        val mesh = Mesh(posArr,texCoordArr,normArr,indiceArr, Material())
        return mesh
    }

    private fun processFaceVertex(idxGroup: IdxGroup, texCoords: ArrayList<Vector2f>, normals: ArrayList<Vector3f>, dstIndices: ArrayList<Int>, texCoordArr: FloatArray, normArr: FloatArray) {

        // Set index for vertex coordinates
        val posIdx = idxGroup.idxPos
        dstIndices.add(posIdx)

        // Reorder texture coordinates
        if (idxGroup.idxTexCoord >= 0){
            val texCoord = texCoords[idxGroup.idxTexCoord]
            texCoordArr[posIdx * 2] = texCoord.x
            texCoordArr[posIdx * 2 + 1] = 1 - texCoord.y
        }

        if (idxGroup.idxNormal >= 0){
            // Reorder vectornormals
            val norm = normals[idxGroup.idxNormal]
            normArr[posIdx * 3] = norm.x
            normArr[posIdx * 3 + 1] = norm.y
            normArr[posIdx * 3 + 2] = norm.z
        }
    }
}